package org.xx.armory.commons;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.function.Supplier;

import static java.util.Arrays.asList;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

/**
 * 执行参数验证的工具类。
 */
public final class Validators {
    private Validators() {
        throw new AssertionError();
    }

    /**
     * 如果指定的表达式的结果是 {@code true} ，则抛出 {@link IllegalArgumentException} 异常。
     *
     * @param expression
     *         待检测的表达式。
     * @throws IllegalArgumentException
     *         如果参数 {@code expression} 的计算结果是 {@code true} 。
     */
    public static void rejectIf(
            Supplier<Boolean> expression
    ) {
        rejectIf(expression, null, null);
    }

    /**
     * 如果指定的表达式的结果是 {@code true} ，则抛出 {@link IllegalArgumentException} 异常。
     *
     * @param expression
     *         待检测的表达式。
     * @param message
     *         校验异常的说明信息。
     * @throws IllegalArgumentException
     *         如果参数 {@code expression} 的计算结果是 {@code true} 。
     */
    public static void rejectIf(
            Supplier<Boolean> expression,
            String message
    ) {
        rejectIf(expression, message, null);
    }

    /**
     * 如果指定的表达式的结果是 {@code true} ，则抛出 {@link IllegalArgumentException} 异常。
     *
     * @param expression
     *         待检测的表达式。
     * @param argumentName
     *         参数名。
     * @param message
     *         校验异常的说明信息。
     * @throws IllegalArgumentException
     *         如果参数 {@code expression} 的计算结果是 {@code true} 。
     */
    public static void rejectIf(
            Supplier<Boolean> expression,
            String message,
            String argumentName
    ) {
        if (expression == null) {
            throw new NullPointerException("Expression must not be null");
        }

        if (expression.get()) {
            message = trimToEmpty(message);
            argumentName = trimToEmpty(argumentName);
            if (!argumentName.isEmpty()) {
                message = "Illegal argument \"" + argumentName + "\", " + message;
            }

            throw new IllegalArgumentException(message);
        }
    }

    public static <T> T rejectIfNull(
            T argument,
            String argumentName
    ) {
        rejectIf(() -> argument == null, "Value must not be null", argumentName);
        return argument;
    }

    public static <T extends Collection<?>> T rejectIfEmpty(
            T argument,
            String argumentName
    ) {
        rejectIf(() -> argument == null || argument.size() == 0, null, argumentName);

        return argument;
    }

    public static int[] rejectIfEmpty(
            int[] argument,
            String argumentName
    ) {
        rejectIf(() -> argument == null || argument.length == 0, "Value must not be empty", argumentName);

        return argument;
    }

    public static long[] rejectIfEmpty(
            long[] argument,
            String argumentName
    ) {
        rejectIf(() -> argument == null || argument.length == 0, "Value must not be empty", argumentName);

        return argument;
    }

    public static String[] rejectIfEmpty(
            String[] argument,
            String argumentName
    ) {
        rejectIf(() -> argument == null || argument.length == 0, "Value must not be empty", argumentName);

        return argument;
    }

    public static <T extends CharSequence> T rejectIfBlank(
            T argument,
            String argumentName
    ) {
        rejectIf(() -> StringUtils.isBlank(argument), "Value must not be blank", argumentName);

        return argument;
    }

    public static int rejectIfNegative(
            int i,
            String argumentName
    ) {
        rejectIf(() -> i < 0, "Value must not be negative, but " + i + " got", argumentName);

        return i;
    }

    public static long rejectIfNegative(
            long l,
            String argumentName
    ) {
        rejectIf(() -> l < 0L, "Value must not be negative, but " + l + " got", argumentName);

        return l;
    }

    public static double rejectIfNegative(
            double d,
            String argumentName
    ) {
        rejectIf(() -> d < 0D, "Value must not be negative, but " + d + " got", argumentName);

        return d;
    }

    public static BigDecimal rejectIfNegative(
            BigDecimal d,
            String argumentName
    ) {
        rejectIf(() -> d == null, "Value must not be null", argumentName);
        rejectIf(() -> d.signum() < 0, "Value must not be negative, but " + d + " got", argumentName);

        return d;
    }

    public static int rejectIfNonPositive(
            int i,
            String argumentName
    ) {
        rejectIf(() -> i <= 0, "Value must not be zero or negative, but " + i + " got", argumentName);

        return i;
    }

    public static long rejectIfNonPositive(
            long l,
            String argumentName
    ) {
        rejectIf(() -> l <= 0L, "Value must not be zero or negative, but " + l + " got", argumentName);

        return l;
    }

    public static double rejectIfNonPositive(
            double d,
            String argumentName
    ) {
        rejectIf(() -> d <= 0D, "Value must not be negative, but " + d + " got", argumentName);

        return d;
    }

    public static BigDecimal rejectIfNonPositive(
            BigDecimal d,
            String argumentName
    ) {
        rejectIf(() -> d == null, "Value must not be null", argumentName);
        rejectIf(() -> d.signum() <= 0, "Value must not be negative, but " + d + " got", argumentName);

        return d;
    }

    public static int rejectIfOutOfRange(
            int i,
            int lowerBound,
            int upperBound,
            String argumentName
    ) {
        rejectIf(() -> i < lowerBound || i >= upperBound, "Value out of bound [" + lowerBound + "," + (upperBound - 1) + "]", argumentName);

        return i;
    }

    public static long rejectIfOutOfRange(
            long l,
            long lowerBound,
            long upperBound,
            String argumentName
    ) {
        rejectIf(() -> l < lowerBound || l >= upperBound, "Value out of bound [" + lowerBound + "," + (upperBound - 1) + "]", argumentName);

        return l;
    }

    public static <T> T rejectIfNotIn(
            T value,
            T[] expectedValues,
            String argumentName
    ) {
        final var expectedValuesList = expectedValues != null ? asList(expectedValues) : asList(new Object[]{null});

        rejectIf(() -> !expectedValuesList.contains(value), "Value must in expected set, but " + value + " got", argumentName);

        return value;
    }

    public static int rejectIfNotIn(
            int value,
            int[] expectedValues,
            String argumentName
    ) {

        rejectIf(() -> expectedValues == null || !ArrayUtils.contains(expectedValues, value), "Value must in expected set, but " + value + " got", argumentName);

        return value;
    }

    public static <T> T rejectIfNotInstance(
            Object value,
            Class<T> clazz,
            String argumentName
    ) {
        rejectIf(() -> !clazz.isInstance(value), "Value must be type of " + clazz.getTypeName() + ", but " + value + " got");

        return clazz.cast(value);
    }
}
